from itertools import chain

class Package:
    packages = {}

    def __init__(self):
        self.bottom = {}
        self.stack = [self.bottom]
        self.path = ''
        self.builtins = {}
        self.keywords = {}

    def copy(self, path):
        if path in Package.packages:
            return Package.packages[path]
        ret = Package()
        ret.keywords = self.keywords.copy()
        ret.builtins = self.builtins.copy()
        return ret

    def import_(self, source: str, *args):
        from fun.parse import parse
        for k, v in self.builtins.items():
            self.stack[0][k] = v
        ast = parse(source, self)
        return ast.execute().no_push(*args)

    def find(self, name: str):
        for layer in self.stack[::-1]:
            if name in layer:
                return layer[name]
        return __builtins__.get(name)

    def set(self, name: str, value):
        for layer in self.stack[::-1]:
            if name in layer:
                layer[name] = value
                return
        self.dec(name, value)

    def dec(self, name: str, value):
        self.stack[-1][name] = value

    def push(self, caller):
        self.stack.append({'self': caller})

    def pop(self):
        self.stack.pop()

    def keyword(self, name, macro=True):
        def deco(decorated):
            if macro:
                decorated.__macro__ = True
            self.keywords[name] = decorated
            return decorated
        return deco

    def builtin(self, name, macro=False):
        def deco(decorated):
            if macro:
                decorated.__macro__ = True
            self.builtins[name] = decorated
            return decorated
        return deco
class Object:
    def __init__(self):
        self.dict = {}

    def __getattribute__(self, name: str):
        if name in self.dict:
            return self.dict[name]
        if hasattr(self, 'proto'):
            tmp = getattr(self.proto, name)
            if tmp is not None:
                return tmp

class Function:
    def __init__(self, params, body):
        self.params = params
        self.body = body

    def no_push(self, *args, **kwargs):
        from fun.nodes import Call, truevalue
        for name, value in chain(zip(self.params, args), kwargs):
            Call.current.dec(name, truevalue(value))
        if not self.body:
            return None
        try:
            for expr in self.body:
                ret = expr.execute()
        except FunctionReturn as ex:
            return ex.ret
        return ret

    def __call__(self, *args, **kwargs):
        from fun.nodes import Call
        Call.current.push(self)
        ret = self.no_push(*args, **kwargs)
        Call.current.pop()
        return ret

class FunctionReturn(Exception):
    def __init__(self, ret):
        self.ret = ret